#include "SyntaxAnalysis.hpp"

SyntaxAnalysis::~SyntaxAnalysis()
{
    /* close files */
    ofs_result.close();
    ofs_variable.close();
    ofs_process.close();
    ofs_error.close();
}

void SyntaxAnalysis::parser(std::filesystem::path sourceFile)
{
    std::cout << "*** Syntax analysis ***" << std::endl
              << " - Source file: " << sourceFile.filename()
              << std::endl;

    loadData(sourceFile);

    /* init data */
    token_number = 1;
    line_number = 1;
    error_number = 0;

    VariablesTable.clear();
    ProcessesTable.clear();
    while (!ProcessStack.empty())
        ProcessStack.pop();

    currentToken = inputTokens.begin();
    /* skip EOLN */
    while (currentToken->second == EOLN_TYPE)
    {
        currentToken++;
        token_number++;
        line_number++;
#ifdef SYN_SAVE_TO_DYS_BYLINE
        ofs_result << "            EOLN 24" << std::endl;
#endif
    }

    /* 递归下降分析 */
    try
    {
        parseProgram();
    }
    catch (const std::invalid_argument &e)
    {
        errorOutput(e.what(), "");
        std::cerr << "\033[1;31m 语法分析终止 !\033[0m"
                  << std::endl;
    }
    catch (const std::out_of_range &e)
    {
        std::cerr << "\033[1;33m " << e.what() << " !\033[0m"
                  << std::endl;
    }

    /* output scanning result */
    std::cout << "*** File syntax analysis completed! ***" << std::endl
              << error_number << " errors found in "
              << token_number << " tokens"
              << std::endl;

    /* close files */
    ofs_variable.close();
    ofs_process.close();
    ofs_error.close();
}

void SyntaxAnalysis::loadData(std::filesystem::path sourceFile)
{
    /* sourceFile path validity */
    if (!std::filesystem::exists(sourceFile))
    {
        std::cerr << "source file path invalid!!!" << std::endl;
        std::exit(EXIT_FAILURE);
    }
    if (sourceFile.extension() != ".dyd")
    {
        std::cerr << "source file type: *.dyd" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    /* lexAnalysis correction */
    std::filesystem::path errorInfo = sourceFile;
    errorInfo.replace_extension(".err");
    if (!std::filesystem::exists(errorInfo))
    {
        /* lexAnalysis */
        LexAnalysis lexAnalysis;
        lexAnalysis.lineScanner(sourceFile);
    }
    if (std::filesystem::file_size(errorInfo) != 0)
    {
        std::cerr << "There are lexical error(s) in " << sourceFile.filename()
                  << " that need to be corrected!" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    /* open sourceFile */
    ifs_source.open(sourceFile, std::ios::in);
    if (!ifs_source.is_open())
    {
        std::cerr << sourceFile.string() + " open failed!!!" << std::endl;
        std::exit(EXIT_FAILURE);
    }

    /* create .dys & .var & .pro & .err */
    ofs_result.open(sourceFile.replace_extension(".dys"), std::ios::trunc);
    ofs_variable.open(sourceFile.replace_extension(".var"), std::ios::trunc);
    ofs_process.open(sourceFile.replace_extension(".pro"), std::ios::trunc);
    ofs_error.open(sourceFile.replace_extension(".err"), std::ios::app);

    /* load inputTokens from .dyd */
    inputTokens.clear();
    std::string token;
    int type;
    while (ifs_source >> token >> type)
    {
#ifdef SYN_SAVE_TO_DYS_BYLINE
        inputTokens.push_back(std::make_pair(token, type));
#else
        /* only IDENTIFIER & CONSTANT save token */
        if (type < IDENTIFIER_TYPE || type > CONSTANT_TYPE)
            inputTokens.push_back(std::make_pair(std::string(), type));
        else
            inputTokens.push_back(std::make_pair(token, type));
#endif
    }

#ifndef SYN_SAVE_TO_DYS_BYLINE
    /* save to .dys */
    ifs_source.clear();
    ifs_source.seekg(0, std::ios::beg);
    std::string line_str;
    while (std::getline(ifs_source, line_str))
        ofs_result << line_str << std::endl;
    ofs_result.close();
#endif

    ifs_source.close();
}

/*
 * 终结符匹配
 * terminal_type = 0 -> get next token
 * True:
 *  - currentToken == terminal_type
 *  - terminal_type <= currentToken <= terminal_type2
 */
bool SyntaxAnalysis::matchTerminal(int terminal_type, int terminal_type2)
{
    /* match Terminal by terminal_type */
    if (terminal_type2)
    {
        if ((currentToken->second < terminal_type) || (currentToken->second > terminal_type2))
            return false;
    }
    else if (terminal_type && currentToken->second != terminal_type)
        return false;

    /* get next token & skip EOLN */
    line_number--;
    do
    {

#ifdef SYN_SAVE_TO_DYS_BYLINE
        /* save to .dys */
        ofs_result << std::setw(16) << std::setfill(' ') << std::right << currentToken->first
                   << ' '
                   << std::setw(2) << std::setfill('0') << currentToken->second
                   << std::endl;
#endif

        currentToken++;
        if (currentToken == inputTokens.end() &&
            (currentToken - 1)->second != EOF_TYPE)
            throw std::out_of_range("currentToken out of range");

        token_number++;
        line_number++;
    } while (currentToken->second == EOLN_TYPE);

    return true;
}

/*
 * 终结符查找判断类型
 * - 不改变 currentToken
 * - terminal_type = 0 : 只考虑terminal_type2
 * - terminal_type2: 下一个终结符
 */
bool SyntaxAnalysis::searchTerminal(int terminal_type, int terminal_type2)
{
    if (terminal_type && currentToken->second != terminal_type)
        return false;

    /* next terminal */
    auto token_ptr = currentToken;
    if (terminal_type2)
    {
        do /* skip EOLN */
        {
            token_ptr++;
        } while (token_ptr->second == EOLN_TYPE);

        if (token_ptr->second != terminal_type2)
            return false;
    }

    return true;
}

/*
 * 发生错误时跳到下一个 `;` 或 `end` 的位置
 */
void SyntaxAnalysis::skipErrors()
{
    while ((currentToken->second != SEMICOLON_TYPE) && (currentToken->second != END_TYPE))
    {

#ifdef SYN_SAVE_TO_DYS_BYLINE
        /* save to .dys */
        ofs_result << std::setw(16) << std::setfill(' ') << std::right << currentToken->first
                   << ' '
                   << std::setw(2) << std::setfill('0') << currentToken->second
                   << std::endl;
#endif

        currentToken++;
        if (currentToken == inputTokens.end())
            throw std::out_of_range("currentToken out of range");

        token_number++;

        if (currentToken->second == EOLN_TYPE)
            line_number++;
    }
}

void SyntaxAnalysis::saveVariable(std::pair<std::string, std::string> variable_pair)
{
    int address = VariablesTable.size() - 1; /* 相对第一个变量而言 */
    int level = ProcessStack.size();

    /* save to .var */
    ofs_variable << variable_pair.first << "\t"  /* 变量名 */
                 << variable_pair.second << "\t" /* 所属过程 */
                 << (level > 1 ? 1 : 0) << "\t"  /* 分类 0—变量 1—形参 */
                 << "ints\t"                     /* 变量类型 ints*/
                 << level << "\t"                /* 变量所属过程层次 */
                 << address                      /* 变量在变量表中的位置 */
                 << std::endl;

    /* process's first & last variable */
    if (ProcessStack.top().firstVariable == -1)
    {
        ProcessStack.top().firstVariable = address;
        ProcessStack.top().lastVariable = address;
    }
    else
        ProcessStack.top().lastVariable = address;
}

void SyntaxAnalysis::saveProcess(std::string process_name)
{
    if (ProcessStack.top().pName != process_name)
    {
        errorOutput("过程层级错误", process_name);
        throw std::invalid_argument("重大语法错误");
    }

    /* save to .pro */
    ofs_process << ProcessStack.top().pName << "\t"         /* 过程名 */
                << "ints\t"                                 /* 过程类型 */
                << ProcessStack.size() << "\t"              /* 过程层次 */
                << ProcessStack.top().firstVariable << "\t" /* 第一个变量在变量表中的位置 */
                << ProcessStack.top().lastVariable          /* 最后一个变量在变量表中的位置 */
                << std::endl;
}

void SyntaxAnalysis::errorOutput(std::string error_type, std::string error_info)
{
    error_number++;
    ofs_error << "***" << line_number << ":  "
              << error_info << " "
              << error_type
              << std::endl;

#ifdef ERRORINFO_STDERR
    std::cerr << "\033[1;31m笨蛋！\033[0m 就...就在那个..."
              << " Line:" << line_number
              << " Token:" << token_number
              << "  \033[0;33m" << error_info 
              << "\t\033[1;34m" << error_type << "\033[0m"
              << std::endl;
#endif
}